/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.bean.timer.interfaces;

import com.bean.timer.TimerEvent;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.event.EventListenerList;

/**
 *
 * @author sammael
 */
public abstract class AbstractTimer extends JPanel implements AbstractTimerControl,AbstractTimeView,AbstractTimerModel{

    public static final String PARAM_NAME_FONTCOLOR = "param_font_color";
    public static final String PARAM_NAME_BACKGROUNDCOLOR = "param_background";
    public static final String PARAM_NAME_VISIBLEHOUR = "param_visible_hour";
    public static final String PARAM_NAME_VISIBLEMINUTE = "param_visible_minute";
    public static final String PARAM_NAME_VISIBLESECOND = "param_visible_second";
    public static final String PARAM_NAME_VISIBLEMSECOND = "param_visible_msecond";
    public static final String PARAM_NAME_FONTSIZE = "param_font_size";
    public static final String PARAM_NAME_LIMIT = "limit";
    public static final String PARAM_NAME_TICK_LEN = "tickLen";
    public static final String PARAM_NAME_COUNT = "tickcount";
    public static final String PARAM_NAME_COUNTDOWN = "countdown";
    
    
    private Color colorFont = Color.GREEN;
    private Color colorBackground = Color.WHITE;
    
    private boolean visibleHour = true;
    private boolean visibleMinute = true;
    private boolean visibleSecond = true;
    private boolean visibleMSecond = true;
    
    protected JLabel labelTime = new JLabel();
    private int size = 60;
    protected Font font;
    long limit;
    AtomicLong atomicElapsedTime;
    //volatile long currentTime;
    int tickLengh;
    int warningTicksCount;
    //protected javax.swing.event.EventListenerList listeners = new javax.swing.event.EventListenerList();
    boolean countingDown;
    ExecutorService executor = Executors.newSingleThreadExecutor();
    Clock clock = new Clock();

    public AbstractTimer() {
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {

            @Override
            public void run() {
               executor.shutdownNow();
               }
            })
        );
    }
        @Override
    public long getLimit() {
        return limit;
    }

    @Override
    public void setLimit(long limit) {
        long old = this.limit;
        this.limit = limit;
        String convertTime = timeFormat();
        labelTime.setText(convertTime);
        propertyChange(PARAM_NAME_LIMIT, old, limit);
    }

    private String timeFormat(){
		
		
		long timeInSec = limit/1000;
		long timeInMin = limit/(1000*60);
		long timeInH = limit/(1000*60*60);
		
		int milis = (int) (limit - (timeInSec*1000));
		int sec =  (int) ((timeInSec) - (timeInMin*60));
		int min = (int) ((timeInMin) - (timeInH*60));
		int h = (int) timeInH;
		
		StringBuilder sb= new StringBuilder();
                if(visibleHour){
                    if(h < 10){
			sb.append(0);
		}
                    sb.append(h).append(":");
                }
		if(visibleMinute){
                    if(min < 10){
			sb.append(0);
		}
                    sb.append(min).append(":");
                }
		
                if(visibleSecond){		
                    if(sec < 10){
                            sb.append(0);
                    }
                    sb.append(sec).append(".");
                }
                if(visibleMSecond){
                    milis = milis/100;
                    sb.append(milis);
                }

		
		return sb.toString();
		
		
	}
    @Override
    public boolean isCountingDown() {
        return countingDown;
    }

    @Override
    public void setCountingDown(boolean countingDown) {
        boolean old = this.countingDown;
        this.countingDown = countingDown;
        propertyChange(PARAM_NAME_COUNTDOWN, old, countingDown);
    }

    
    public long elapsed() {
        return atomicElapsedTime.get();
    }
/*
    @Override
    private void setCurrentTime(long time) {
        throw new UnsupportedOperationException("Not supported yet.");
    }
*/
    @Override
    public void setWarningTicksCount(int ticks) {
        int old = this.warningTicksCount;
        this.warningTicksCount = ticks;
        propertyChange(PARAM_NAME_COUNT, old, ticks);
    }

    @Override
    public int getWarningTicksCount() {
        return warningTicksCount;
    }

    @Override
    public void setTickLength(int milisec) {
        int old = this.tickLengh;
        this.tickLengh = milisec;
        propertyChange(PARAM_NAME_TICK_LEN, old, milisec);
    }

    @Override
    public int getTickLength() {
        return tickLengh;
    }

    @Override
    public void addTimerListener(TimerListener listener) {
        listenerList.add(TimerListener.class,listener);
    }

    @Override
    public void removeTimerListener(TimerListener listener) {
        listenerList.remove(TimerListener.class,listener);
    
    }

    @Override
    public EventListenerList getTimerListeners() {
        return listenerList;
    }

    @Override
    public void setFontColor(Color color){
        Color oldColor = this.colorFont;
        this.colorFont = color;
        labelTime.setForeground(colorFont);
        propertyChange(PARAM_NAME_FONTCOLOR, oldColor, color);
    }
    @Override
    public Color getFontColor(){
        return colorFont;
    }
    
    @Override
    public void setBackgroundColor(Color color){
        Color oldColor = this.colorBackground;
        this.colorBackground = color;
        this.setBackground(colorBackground);
        propertyChange(PARAM_NAME_BACKGROUNDCOLOR, oldColor, color);
    }
    @Override
    public Color getBackgroundColor(){
        return colorBackground;
    }
    
    @Override
    public void setFontSize(int size){
        int oldSize = this.size;
        this.size = size;
        labelTime.setFont(font.deriveFont((float) size));
        propertyChange(PARAM_NAME_FONTSIZE, oldSize, size);
    }
    @Override
    public int getFontSize(){
        return size;
    }
    
    @Override
    public void setHourVisible(boolean visible){
        boolean oldVisible = this.visibleHour;
        this.visibleHour = visible;
        String convertTime = timeFormat();
        labelTime.setText(convertTime);
        propertyChange(PARAM_NAME_VISIBLEHOUR, oldVisible, visible);
    }
    @Override
    public boolean isHourVisible(){
        return visibleHour;
    }
    
    @Override
    public void setMinuteVisible(boolean visible){
        boolean oldVisible = this.visibleMinute;
        this.visibleMinute = visible;
        String convertTime = timeFormat();
        labelTime.setText(convertTime);
        propertyChange(PARAM_NAME_VISIBLEMINUTE, oldVisible, visible);
    }
    @Override
    public boolean isMinuteVisible(){
        return visibleMinute;
    }
    
    @Override
    public void setSecondVisible(boolean visible){
        boolean oldVisible = this.visibleSecond;
        this.visibleSecond = visible;
        String convertTime = timeFormat();
        labelTime.setText(convertTime);
        propertyChange(PARAM_NAME_VISIBLESECOND, oldVisible, visible);
    }
    @Override
    public boolean isSecondVisible(){
        return visibleSecond;
    }
    
    @Override
    public void setMSecondVisible(boolean visible){
        boolean oldVisible = this.visibleMSecond;
        this.visibleMSecond = visible;
        String convertTime = timeFormat();
        labelTime.setText(convertTime);
        propertyChange(PARAM_NAME_VISIBLEMSECOND, oldVisible, visible);
    }
    @Override
    public boolean isMSecondVisible(){
        return visibleMSecond;
    }
    
    @Override
    public void startTimer() {
        executor.execute(clock);
        timerOnStartEventOccured();
    }

    @Override
    public void stopTimer() {
        clock.stop();
        timerOnStopEventOccured();
    }

    @Override
    public void resetTimer() {
        atomicElapsedTime.set(0);
        timerOnResetEventOccured();
    }
    
    @Override
    protected abstract void paintComponent(Graphics g);
        
    protected abstract void propertyChange(String property,long oldval, long newval);
    protected abstract void propertyChange(String property,int oldval, int newVal);
    protected abstract void propertyChange(String property,Object oldval, Object newVal);
    protected abstract void propertyChange(String property,boolean oldval, boolean newVal);
    protected abstract void setCurrentTime(long time);
    
    private synchronized void timerWariningEventOccured(){
        if(countingDown){
        long remaining = limit - atomicElapsedTime.get();
        int tick = (int)(remaining/tickLengh);
        if(tick<=this.warningTicksCount){
            Object[] listeners = listenerList.getListenerList();
            TimerEvent event = new TimerEvent(this);
            event.setRemainingTime(remaining);
            event.setTickNumber(tick);
        for(Object l: listeners){
            if(l==TimerListener.class){
            ((TimerListener)l).onWarningTick(event);
        }
        }
        }
        }
    }
    
    private synchronized void timerTimeoutEventOccured(){
        if(countingDown){
        long remaining = limit - atomicElapsedTime.get();
        if(remaining<=0){
            Object[] listeners = listenerList.getListenerList();
            TimerEvent event = new TimerEvent(this);
            event.setRemainingTime(remaining);
            event.setTickNumber(-1);
        for(Object l: listeners){
            if(l==TimerListener.class){
            ((TimerListener)l).onTimeout(event);
        
            }
            }
          }
        }
    }
    private void timerOnStartEventOccured(){
        
            Object[] listeners = listenerList.getListenerList();
            TimerEvent event = new TimerEvent(this);
            event.setRemainingTime(atomicElapsedTime.get());
            event.setTickNumber(0);
        for(Object l: listeners){
            if(l==TimerListener.class){
            ((TimerListener)l).onStart(event);
        
            }
            }
          
    }
    
    private void timerOnStopEventOccured(){
        
            Object[] listeners = listenerList.getListenerList();
            TimerEvent event = new TimerEvent(this);
            event.setRemainingTime(atomicElapsedTime.get());
            event.setTickNumber(0);
        for(Object l: listeners){
            if(l==TimerListener.class){
            ((TimerListener)l).onStop(event);
        
            }
            }
    }
    
    private void timerOnResetEventOccured(){
        
            Object[] listeners = listenerList.getListenerList();
            TimerEvent event = new TimerEvent(this);
            event.setRemainingTime(atomicElapsedTime.get());
            event.setTickNumber(0);
        for(Object l: listeners){
            if(l==TimerListener.class){
            ((TimerListener)l).onReset(event);
        
            }
            }
    }
    
    public final class Clock implements Runnable{
        private volatile boolean stop;
        public void stop(){
            stop = true;
        }
        @Override
        public void run() {
            stop = false;
            while(!stop){
                try{
                    Thread.sleep(100);
                    atomicElapsedTime.getAndAdd(100);
                }catch(InterruptedException e){
                }
            }
        }
        
    }    
    
    public final class EventMonitor implements Runnable{

        @Override
        public void run() {
            timerTimeoutEventOccured();
            timerWariningEventOccured();
        }
        
    }
}
